By default, WebViews in webview_flutter
and flutter_inappwebview
allow users to navigate to any URL by clicking links or
through JavaScript-initiated navigations. This behavior contrasts with native Android WebViews, which typically open new URLs in an external browser,
thereby providing users with important context like a visible URL bar. webview_flutter
and similar frameworks deviate from this safer
native default, permitting arbitrary navigation unless explicitly configured otherwise. This means the WebView behaves more like an embedded browser
without the usual security indicators.
Permitting unrestricted navigation within an app’s WebView can lead to significant security vulnerabilities. For instance, an element within the
page (e.g., an ad) could redirect the user to a convincing phishing site designed to steal credentials. Since the WebView is part of the trusted
application and lacks standard browser interface elements, users are more susceptible to such deception. Furthermore, if a page with a Cross-Site
Scripting (XSS) vulnerability is loaded, the ability to navigate freely can expand the attack’s scope or be used to further mislead the user. This
loss of contextual security makes it difficult for users to identify and avoid malicious websites.
Ask Yourself Whether
- Your WebView loads content from arbitrary URLs or third-party sources (e.g., advertisements, external links)
- A malicious redirection within the WebView could lead to a phishing attack or credential theft
- The WebView displays sensitive information or interacts with features that could be compromised if the user is redirected to a malicious site
There is a risk if you answered yes to any of these questions.
Recommended Secure Coding Practices
Restrict Navigation
To prevent navigation to arbitrary URLs, all navigation attempts within the WebView need to be intercepted and validated.
For webview_flutter
, this is done by providing a NavigationDelegate
to the WebViewController
. The
onNavigationRequest
callback in this delegate is called for every navigation attempt (e.g., when a user clicks a link or JavaScript tries
to change the location). This callback allows the application to inspect the target URL and decide whether to allow the navigation by returning
NavigationDecision.navigate
or block it by returning NavigationDecision.prevent
.
For flutter_inappwebview
, the shouldOverrideUrlLoading
callback on the InAppWebView
widget serves a similar
purpose. For this callback to be invoked, the useShouldOverrideUrlLoading
option in InAppWebViewSettings
must be set to
true
. When a navigation is attempted, this callback receives details about the navigation action. The application can then return
NavigationActionPolicy.ALLOW
to permit the navigation or NavigationActionPolicy.CANCEL
to block it.
By implementing these respective callbacks, developers can enforce a strict navigation policy, such as blocking all navigations (most secure for
static content WebViews), allowing navigations only to a predefined set of trusted domains (allow-listing), or deciding to open certain links in an
external browser application rather than within the WebView itself.
Sensitive Code Example
The default configurations for both webview_flutter
and flutter_inappwebview
allow arbitrary navigation.
In webview_flutter
, setting no NavigationDelegate
or using the default one will allow the user to navigate to any
URL.
import 'package:webview_flutter/webview_flutter.dart';
final WebViewController controller = WebViewController() // Sensitive - all navigations are permitted by default
..setJavaScriptMode(JavaScriptMode.unrestricted)
..loadRequest(Uri.parse('https://example.com/'));
In flutter_inappwebview
, all navigation is permitted when useShouldOverrideUrlLoading
is set to false
or
when no shouldOverrideUrlLoading
callback is provided:
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
InAppWebView(
initialUrlRequest: URLRequest(url: WebUri('https://example.com/')),
initialSettings: InAppWebViewSettings( // Sensitive - all navigations are permitted by default
javaScriptEnabled: true,
),
);
Compliant Solution
In webview_flutter
:
import 'package:webview_flutter/webview_flutter.dart';
final WebViewController controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setNavigationDelegate(
NavigationDelegate(
onNavigationRequest: (NavigationRequest request) {
final Uri uri = Uri.parse(request.url);
if (uri.host == 'example.com') {
return NavigationDecision.navigate;
}
return NavigationDecision.prevent;
},
),
)
..loadRequest(Uri.parse('https://example.com/'));
In flutter_inappwebview
:
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
InAppWebView(
initialUrlRequest: URLRequest(url: WebUri('https://example.com/')),
initialSettings: InAppWebViewSettings(
javaScriptEnabled: true,
useShouldOverrideUrlLoading: true,
),
shouldOverrideUrlLoading: (controller, navigationAction) async {
final uri = navigationAction.request.url;
if (uri != null && uri.host == 'example.com') {
return NavigationActionPolicy.ALLOW;
}
return NavigationActionPolicy.CANCEL;
},
);
See
Related rules
- S6362 - Enabling JavaScript support for WebViews is security-sensitive